﻿using System;
using System.IO;
using System.Text.RegularExpressions;

using Microsoft.TeamFoundation;
using Microsoft.TeamFoundation.Client;
using Microsoft.TeamFoundation.Server;
using Microsoft.TeamFoundation.VersionControl;
using Microsoft.TeamFoundation.VersionControl.Client;
using Microsoft.TeamFoundation.VersionControl.Common;

namespace TeamFoundationServerSensor {

    /// <summary>
    /// Summary storing the differences between two files.
    /// </summary>
    public class ItemDifference {

        #region Fields
        /// <summary>
        /// The source item. 
        /// </summary>
        private Item source;

        /// <summary>
        /// The target item that the source item will be compared against. 
        /// </summary>
        private Item target;

        /// <summary>
        /// # of lines added.
        /// </summary>
        private int linesAdded;

        /// <summary>
        /// # of lines deleted.
        /// </summary>
        private int linesDeleted;

        /// <summary>
        /// # of lines changed.
        /// </summary>
        private int linesChanged;
        #endregion

        /// <summary>
        /// Constructor for comparing two versioned Items.
        /// </summary>
        /// <param name="source">The source Item</param>
        /// <param name="target">The target Item</param>
        public ItemDifference(Item source, Item target) {
            this.source = source;
            this.target = target;

            linesAdded = 0;
            linesDeleted = 0;
            linesChanged = 0;

            computeDifferences();
        }

        /// <summary>
        /// Computes the differences between the two items.
        /// </summary>
        private void computeDifferences() {

            // Get the original item to compare.
            IDiffItem itemSource = new DiffItemVersionedFile(source,
                new ChangesetVersionSpec(source.ChangesetId));
            IDiffItem itemTarget = new DiffItemVersionedFile(target,
                new ChangesetVersionSpec(target.ChangesetId));

            // Create stream for files comparision.
            MemoryStream memStream = new MemoryStream();
            StreamWriter strw = new StreamWriter(memStream);
            strw.AutoFlush = true;

            // Specify the options when comparing. Outputs, etc.
            DiffOptions options = new DiffOptions();
            options.UseThirdPartyTool = false;
            options.Flags = DiffOptionFlags.EnablePreambleHandling;
            options.OutputType = DiffOutputType.UnixNormal;
            options.TargetEncoding = strw.Encoding;
            options.SourceEncoding = strw.Encoding;
            options.StreamWriter = strw;

            // Compare the two files.
            Difference.DiffFiles(source.VersionControlServer, itemTarget, itemSource, options, source.ServerItem, true);
            string output = strw.Encoding.GetString(memStream.GetBuffer());

            // Parse the output stream to determine the number of lines added,
            // changed and deleted.

            // Split on the newlines and search for the descriptive lines
            // which contain the information about whether the change was an
            // add, delete, or change.
            string[] lines = output.Split("\r\n".ToCharArray());
            foreach (string line in lines) {
                Match match = Regex.Match(line, @"^(\d+)(,\d*)*([cad])(\d+)(,\d*)*$");
                
                // Found a matching line.
                if (match.Success) {
                    
                    GroupCollection gpc = match.Groups;

                    // Determine the number of lines affected.
                    // VERY! bad coding but it works.
                    int count = 1;
                    int differencecount = 0;
                    Group gp = gpc[0];
                    foreach (Group temp in gpc) {
                        if (Regex.IsMatch(temp.Value, @"^[cad]$")) {
                            gp = temp;
                            if (!temp.Value.Equals("a")) {
                                break;
                            }
                            else {
                                differencecount = 0;
                            }
                        }

                        // Attempt to extract the number of lines changed.
                        if (Regex.IsMatch(temp.Value, @"^\d+$")) {
                            differencecount -= int.Parse(temp.Value);
                        }
                        else if (temp.Value.Length > 0 && temp.Value[0].Equals(',')) {
                            differencecount += int.Parse(temp.Value.Substring(1, temp.Value.Length - 1));
                            count = differencecount + 1;
                        }
                    }

                    // Determine what type of match it was.
                    switch (gp.Value) {
                        case "a":
                            linesAdded += count;
                            break;
                        case "c":
                            linesChanged += count;
                            break;
                        case "d":
                            linesDeleted += count;
                            break;
                    }
                }
            }          
        }

        #region Get and Set
        /// <summary>
        /// Get and set methods for source.
        /// </summary>
        public Item Source {
            get { return this.source; }
        }

        /// <summary>
        /// Get and set methods for target.
        /// </summary>
        public Item Target {
            get { return this.target; }
        }

        /// <summary>
        /// Get and set methods for linesAdded.
        /// </summary>
        public int LinesAdded {
            get { return this.linesAdded; }
        }

        /// <summary>
        /// Get and set methods for linesDeleted.
        /// </summary>
        public int LinesDeleted {
            get { return this.linesDeleted; }
        }

        /// <summary>
        /// Get and set methods for linesChanged.
        /// </summary>
        public int LinesChanged {
            get { return this.linesChanged; }
        }
        #endregion
    }
}